﻿// Source adapted from http://vortexwolf.wordpress.com/2011/04/09/silverlight-tabcontrol-with-data-binding/
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Outcoder.UI.Xaml.Controls
{
	/// <summary>
	/// This class improves upon the TabControl and behaves more like the WPF TabControl.
	/// It enables you to bind the ItemsSource and SelectedItem property to a viewmodel property.
	/// <example>
	///	&lt;c:TabControlEnhanced Grid.Row="1"
	///					ItemsSource="{Binding ViewModels}"
	///					SelectedItem="{Binding SelectedViewModel, Mode=TwoWay}"&gt;
	///		&lt;c:TabControlEnhanced.ItemTemplate&gt;
	///			&lt;DataTemplate&gt;
	///				&lt;TextBlock Text="{Binding Title}" 
	///							FontSize="{StaticResource FontSizeSmall}" /&gt;
	///			&lt;/DataTemplate&gt;
	///		&lt;/c:TabControlEnhanced.ItemTemplate&gt;
	///		&lt;c:TabControlEnhanced.ContentTemplate&gt;
	///			&lt;DataTemplate&gt;
	///				&lt;ContentControl Grid.Column="1" 
	///								Content="{Binding}"
	///								HorizontalContentAlignment="Stretch" 
	///								VerticalContentAlignment="Stretch"
	///								HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/&gt;
	///			&lt;/DataTemplate&gt;
	///		&lt;/c:TabControlEnhanced.ContentTemplate&gt;
	///	&lt;/c:TabControlEnhanced&gt;
	/// </example>
	/// </summary>
	public class TabControlEnhanced : TabControl
	{
		public TabControlEnhanced()
		{
			DefaultStyleKey = typeof(TabControl);
			SelectionChanged += OnSelectionChanged;
		}

		public new static readonly DependencyProperty ItemTemplateProperty
			= DependencyProperty.Register(
				"ItemTemplate",
				typeof(DataTemplate),
				typeof(TabControlEnhanced),
				new PropertyMetadata((sender, e) => ((TabControlEnhanced)sender).InitTabs()));

		public static readonly DependencyProperty ContentTemplateProperty
			= DependencyProperty.Register(
				"ContentTemplate",
				typeof(DataTemplate),
				typeof(TabControlEnhanced),
				new PropertyMetadata((sender, e) => ((TabControlEnhanced)sender).InitTabs()));

		public new static readonly DependencyProperty ItemsSourceProperty
			= DependencyProperty.Register(
				"ItemsSource",
				typeof(IEnumerable),
				typeof(TabControlEnhanced),
				new PropertyMetadata(OnItemsSourceChanged));

		public new static readonly DependencyProperty SelectedItemProperty
			= DependencyProperty.Register(
				"SelectedItem",
				typeof(object),
				typeof(TabControlEnhanced),
				new PropertyMetadata(OnSelectedItemChanged));

		/// <summary>
		/// Gets or sets a DataTemplate for a TabItem header
		/// </summary>
		public new DataTemplate ItemTemplate
		{
			get
			{
				return (DataTemplate)GetValue(ItemTemplateProperty);
			}
			set
			{
				SetValue(ItemTemplateProperty, value);
			}
		}

		/// <summary>
		/// Gets or sets a DataTemplate for a TabItem content
		/// </summary>
		public DataTemplate ContentTemplate
		{
			get
			{
				return (DataTemplate)GetValue(ContentTemplateProperty);
			}
			set
			{
				SetValue(ContentTemplateProperty, value);
			}
		}

		/// <summary>
		/// Gets or sets a collection used to generate the collection of TabItems
		/// </summary>
		public new IEnumerable ItemsSource
		{
			get
			{
				return (IEnumerable)GetValue(ItemsSourceProperty);
			}
			set
			{
				SetValue(ItemsSourceProperty, value);
			}
		}

		/// <summary>
		/// Gets or sets the first item in the current selection or returns null if the selection is empty
		/// </summary>
		public new object SelectedItem
		{
			get
			{
				return GetValue(SelectedItemProperty);
			}
			set
			{
				SetValue(SelectedItemProperty, value);
			}
		}

		static void OnItemsSourceChanged(
			DependencyObject sender, DependencyPropertyChangedEventArgs e)
		{
			var control = (TabControlEnhanced)sender;
			var incc = e.OldValue as INotifyCollectionChanged;
			if (incc != null)
			{
				incc.CollectionChanged -= control.ItemsSourceCollectionChanged;
			}

			control.InitTabs();

			incc = e.NewValue as INotifyCollectionChanged;
			if (incc != null)
			{
				incc.CollectionChanged += control.ItemsSourceCollectionChanged;
			}
		}

		static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
		{
			var control = (TabControl)sender;
			//Base class, not extended, because we must change the original SelectedItem property

			if (e.NewValue == null)
			{
				control.SelectedItem = null;
			}
			else
			{
				TabItem tab = control.Items.Cast<TabItem>().FirstOrDefault(ti => ti.DataContext == e.NewValue);
				if (tab != null && control.SelectedItem != tab)
				{
					control.SelectedItem = tab;
				}
			}
		}

		/// <summary>
		/// Create the collection of TabItems from the collection of clr-objects
		/// </summary>
		void InitTabs()
		{
			Items.Clear();
			if (ItemsSource == null)
			{
				return;
			}

			foreach (object item in ItemsSource)
			{
				TabItem newitem = CreateTabItem(item);
				Items.Add(newitem);
			}
		}

		/// <summary>
		/// Handles the CollectionChanged event of the ItemsSource property
		/// </summary>
		void ItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			if (e.Action == NotifyCollectionChangedAction.Add)
			{
				if (e.NewItems != null && e.NewStartingIndex > -1)
				{
					foreach (object item in e.NewItems.OfType<object>().Reverse())
					{
						TabItem newitem = CreateTabItem(item);
						Items.Insert(e.NewStartingIndex, newitem);
					}
				}
			}
			else if (e.Action == NotifyCollectionChangedAction.Remove)
			{
				if (e.OldStartingIndex > -1)
				{
					Items.RemoveAt(e.OldStartingIndex);
				}
			}
			else if (e.Action == NotifyCollectionChangedAction.Replace)
			{
				//I don't know how this action can be called. I would rather ignore it.
			}
			else if (e.Action == NotifyCollectionChangedAction.Reset)
			{
				InitTabs();
			}
		}

		/// <summary>
		/// Creates the TabItem object from a clr-object
		/// </summary>
		/// <param name="dataContext">The clr-object which will be set as the DataContext of the TabItem</param>
		/// <returns>The TabItem object</returns>
		TabItem CreateTabItem(object dataContext)
		{
			var newitem = new TabItem();

			var horizontalContentAlignment = new Binding("HorizontalContentAlignment") { Source = this };
			BindingOperations.SetBinding(newitem, HorizontalContentAlignmentProperty, horizontalContentAlignment);

			var verticalContentAlignment = new Binding("VerticalContentAlignment") { Source = this };
			BindingOperations.SetBinding(newitem, VerticalContentAlignmentProperty, verticalContentAlignment);

			newitem.Content = ContentTemplate != null ? ContentTemplate.LoadContent() : dataContext;

			newitem.Header = ItemTemplate != null ? ItemTemplate.LoadContent() : dataContext;

			newitem.DataContext = dataContext;

			return newitem;
		}

		/// <summary>
		/// Update the current selected clr-item from the selected TabItem
		/// </summary>
		void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			TabItem tabItem = e.AddedItems.Cast<TabItem>().FirstOrDefault();
			SelectedItem = tabItem != null ? tabItem.DataContext : null;
		}
	}
}